package org.cloudbus.cloudsim.pam;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.cloudbus.cloudsim.pam.DCManager;
import org.cloudbus.cloudsim.Cloudlet;
import org.cloudbus.cloudsim.CloudletScheduler;
import org.cloudbus.cloudsim.DataCloudTags;
import org.cloudbus.cloudsim.File;
import org.cloudbus.cloudsim.InfoPacket;
import org.cloudbus.cloudsim.Log;
import org.cloudbus.cloudsim.Storage;
import org.cloudbus.cloudsim.Vm;
import org.cloudbus.cloudsim.core.CloudSim;
import org.cloudbus.cloudsim.core.CloudSimTags;
import org.cloudbus.cloudsim.core.SimEntity;
import org.cloudbus.cloudsim.core.SimEvent;

public class PAMDatacenter extends SimEntity {

	private DCManager dcManager;
	
	/** The characteristics. */
	private PAMDatacenterCharacteristics characteristics;

	/** The regional cis name. */
	private String regionalCisName;

	/** The vm provisioner. */
	private PAMVmAllocationPolicy vmAllocationPolicy;

	/** The last process time. */
	private double lastProcessTime;

	/** The debts. */
	private Map<Integer, Double> debts;

	/** The storage list. */
	private List<Storage> storageList;

	/** The vm list. */
	private List<? extends Vm> vmList;

	/** The scheduling interval. */
	private double schedulingInterval;

	/**
	 * Allocates a new PowerDatacenter object.
	 *
	 * @param name       the name to be associated with this entity (as
	 * required by Sim_entity class from simjava package)
	 * @param characteristics   an object of DatacenterCharacteristics
	 * @param storageList a LinkedList of storage elements, for data simulation
	 * @param vmAllocationPolicy the vmAllocationPolicy
	 *
	 * @throws Exception This happens when one of the following scenarios occur:
	 * <ul>
	 * <li> creating this entity before initializing CloudSim package
	 * <li> this entity name is <tt>null</tt> or empty
	 * <li> this entity has <tt>zero</tt> number of PEs (Processing
	 * Elements). <br>
	 * No PEs mean the Cloudlets can't be processed.
	 * A CloudResource must contain one or more Machines.
	 * A Machine must contain one or more PEs.
	 * </ul>
	 *
	 * @pre name != null
	 * @pre resource != null
	 * @post $none
	 */

	public PAMDatacenter(String name,
			PAMDatacenterCharacteristics characteristics,
			PAMVmAllocationPolicy vmAllocationPolicy, List<Storage> storageList,
			double schedulingInterval) throws Exception {
		
		super(name);

		setCharacteristics(characteristics);
		setPAMVmAllocationPolicy(vmAllocationPolicy);
		setLastProcessTime(0.0);
		setDebts(new HashMap<Integer,Double>());
		setStorageList(storageList);
		setVmList(new ArrayList<Vm>());
		setSchedulingInterval(schedulingInterval);

        // If this resource doesn't have any PEs then no useful at all
        if (getCharacteristics().getPesNumber() == 0) {
            throw new Exception(super.getName() + " : Error - this entity has no PEs. Therefore, can't process any Cloudlets.");
        }

        // stores id of this class
        getCharacteristics().setId(super.getId());
	
	}

    /**
     * Overrides this method when making a new and different type of resource.
     * This method is called by {@link #body()} to register other type to
     * GIS entity. In doing so, you
     * need to create a new child class extending from
     * gridsim.CloudInformationService.
     * <br>
     * <b>NOTE:</b> You do not need to override {@link #body()} method, if
     * you use this method.
     *
     * @pre $none
     * @post $none
     */
    protected void registerOtherEntity() {
        // empty. This should be override by a child class
    }



	   @Override
		public void processEvent(SimEvent ev) {
	        int srcId = -1;
	        //Log.printLine(CloudSim.clock()+"[PowerDatacenter]: event received:"+ev.getTag());

	        switch (ev.getTag()) {
	            // Resource characteristics inquiry
	            case CloudSimTags.RESOURCE_CHARACTERISTICS:
	                srcId = ((Integer) ev.getData()).intValue();
	                sendNow(srcId, ev.getTag(), getCharacteristics());
	                break;

	                // Resource dynamic info inquiry
	            case CloudSimTags.RESOURCE_DYNAMICS:
	                srcId = ((Integer) ev.getData()).intValue();
	                sendNow(srcId, ev.getTag(), 0);
	                break;

	            case CloudSimTags.RESOURCE_NUM_PE:
	                srcId = ((Integer) ev.getData()).intValue();
	                int numPE = getCharacteristics().getPesNumber();
	                sendNow(srcId, ev.getTag(), numPE);
	                break;

	            case CloudSimTags.RESOURCE_NUM_FREE_PE:
	                srcId = ((Integer) ev.getData()).intValue();
	                int freePesNumber = getCharacteristics().getFreePesNumber();
	                sendNow(srcId, ev.getTag(), freePesNumber);
	                break;

	                // New Cloudlet arrives
	            case CloudSimTags.CLOUDLET_SUBMIT:
	                processCloudletSubmit(ev, false);
	                break;

	                // New Cloudlet arrives, but the sender asks for an ack
	            case CloudSimTags.CLOUDLET_SUBMIT_ACK:
	                processCloudletSubmit(ev, true);
	                break;

	                // Cancels a previously submitted Cloudlet
	            case CloudSimTags.CLOUDLET_CANCEL:
	                processCloudlet(ev, CloudSimTags.CLOUDLET_CANCEL);
	                break;

	                // Pauses a previously submitted Cloudlet
	            case CloudSimTags.CLOUDLET_PAUSE:
	                processCloudlet(ev, CloudSimTags.CLOUDLET_PAUSE);
	                break;

	                // Pauses a previously submitted Cloudlet, but the sender
	                // asks for an acknowledgement
	            case CloudSimTags.CLOUDLET_PAUSE_ACK:
	                processCloudlet(ev, CloudSimTags.CLOUDLET_PAUSE_ACK);
	                break;

	                // Resumes a previously submitted Cloudlet
	            case CloudSimTags.CLOUDLET_RESUME:
	                processCloudlet(ev, CloudSimTags.CLOUDLET_RESUME);
	                break;

	                // Resumes a previously submitted Cloudlet, but the sender
	                // asks for an acknowledgement
	            case CloudSimTags.CLOUDLET_RESUME_ACK:
	                processCloudlet(ev, CloudSimTags.CLOUDLET_RESUME_ACK);
	                break;

	                // Moves a previously submitted Cloudlet to a different resource
	            case CloudSimTags.CLOUDLET_MOVE:
	                processCloudletMove((int[]) ev.getData(), CloudSimTags.CLOUDLET_MOVE);
	                break;

	                // Moves a previously submitted Cloudlet to a different resource
	            case CloudSimTags.CLOUDLET_MOVE_ACK:
	                processCloudletMove((int[]) ev.getData(), CloudSimTags.CLOUDLET_MOVE_ACK);
	                break;

	                // Checks the status of a Cloudlet
	            case CloudSimTags.CLOUDLET_STATUS:
	                processCloudletStatus(ev);
	                break;

	                // Ping packet
	            case CloudSimTags.INFOPKT_SUBMIT:
	                processPingRequest(ev);
	                break;

	           	case CloudSimTags.VM_CREATE:
	        		processVmCreate(ev, false);
	        		break;

	        	case CloudSimTags.VM_CREATE_ACK:
	        		processVmCreate(ev, true);
	        		break;

	           	case CloudSimTags.VM_DESTROY:
	           		processVmDestroy(ev, false);
	           		break;

	           	case CloudSimTags.VM_DESTROY_ACK:
	        		processVmDestroy(ev, true);
	        		break;

	           	case CloudSimTags.VM_MIGRATE:
	           		processVmMigrate(ev, false);
	           		break;

	           	case CloudSimTags.VM_MIGRATE_ACK:
	           		processVmMigrate(ev, true);
	           		break;

	           	case CloudSimTags.VM_DATA_ADD:
	           		processDataAdd(ev, false);
	           		break;

	           	case CloudSimTags.VM_DATA_ADD_ACK:
	           		processDataAdd(ev, true);
	           		break;

	           	case CloudSimTags.VM_DATA_DEL:
	           		processDataDelete(ev, false);
	           		break;

	           	case CloudSimTags.VM_DATA_DEL_ACK:
	           		processDataDelete(ev, true);
	           		break;

	           	case CloudSimTags.VM_DATACENTER_EVENT:
	           		updateCloudletProcessing();
	            	checkCloudletCompletion();
	           		break;

	           		// other unknown tags are processed by this method
	            default:
	                processOtherEvent(ev);
	                break;
	        }
	    }
	   

	    /**
	     * Process data del.
	     *
	     * @param ev the ev
	     * @param ack the ack
	     */
	    protected void processDataDelete(SimEvent ev, boolean ack) {
	        if (ev == null) {
	            return;
	        }

	        Object[] data = (Object[]) ev.getData();
	        if (data == null) {
	            return;
	        }

	        String filename = (String) data[0];
	        int req_source = ((Integer) data[1]).intValue();
	        int tag = -1;

	        // check if this file can be deleted (do not delete is right now)
	        int msg = deleteFileFromStorage(filename);
	        if (msg == DataCloudTags.FILE_DELETE_SUCCESSFUL) {
	           tag = DataCloudTags.CTLG_DELETE_MASTER;
	        } else { // if an error occured, notify user
	           tag = DataCloudTags.FILE_DELETE_MASTER_RESULT;
	        }

	    	if (ack){
	            // send back to sender
	            Object pack[] = new Object[2];
	            pack[0] = filename;
	            pack[1] = Integer.valueOf(msg);

	            sendNow(req_source, tag, pack);
	    	}
		}

		/**
		 * Process data add.
		 *
		 * @param ev the ev
		 * @param ack the ack
		 */
		protected void processDataAdd(SimEvent ev, boolean ack) {
	       if (ev == null) {
	            return;
	        }

	        Object[] pack = (Object[]) ev.getData();
	        if (pack == null) {
	            return;
	        }

	        File file = (File) pack[0]; // get the file
	        file.setMasterCopy(true); // set the file into a master copy
	        int sentFrom = ((Integer) pack[1]).intValue(); // get sender ID

	        /******     // DEBUG
	         Log.printLine(super.get_name() + ".addMasterFile(): " +
	         file.getName() + " from " + CloudSim.getEntityName(sentFrom));
	         *******/

	        Object[] data = new Object[3];
	        data[0] = file.getName();

	        int msg = addFile(file); // add the file

	        double debit;
	        if (getDebts().containsKey(sentFrom)) {
	        	debit = getDebts().get(sentFrom);
	        } else {
	        	debit = 0.0;
	        }

	        debit += getCharacteristics().getCostPerBw() * file.getSize();

	        getDebts().put(sentFrom, debit);

	        if (ack) {
	        	data[1] = Integer.valueOf(-1); // no sender id
	        	data[2] = Integer.valueOf(msg); // the result of adding a master file
	        	sendNow(sentFrom, DataCloudTags.FILE_ADD_MASTER_RESULT, data);
	        }
		}

		/**
		 * Processes a ping request.
		 *
		 * @param ev  a Sim_event object
		 *
		 * @pre ev != null
		 * @post $none
		 */
	    protected void processPingRequest(SimEvent ev) {
	        InfoPacket pkt = (InfoPacket) ev.getData();
	        pkt.setTag(CloudSimTags.INFOPKT_RETURN);
	        pkt.setDestId(pkt.getSrcId());

	        // sends back to the sender
	        sendNow(pkt.getSrcId(), CloudSimTags.INFOPKT_RETURN, pkt);
	    }

	    /**
	     * Process the event for an User/Broker who wants to know the status of a Cloudlet.
	     * This PowerDatacenter will then send the status back to the User/Broker.
	     *
	     * @param ev   a Sim_event object
	     *
	     * @pre ev != null
	     * @post $none
	     */
	    protected void processCloudletStatus(SimEvent ev) {
	        int cloudletId = 0;
	        int userId = 0;
	        int vmId = 0;
	        int status = -1;

	        try{
	            // if a sender using cloudletXXX() methods
	            int data[] = (int[]) ev.getData();
	            cloudletId = data[0];
	            userId = data[1];
	            vmId = data[2];

	            status = getPAMVmAllocationPolicy().getHost(vmId, userId).getVm(userId, vmId).getCloudletScheduler().getCloudletStatus(cloudletId);
	        }

	        // if a sender using normal send() methods
	        catch (ClassCastException c) {
	            try {
	                Cloudlet cl = (Cloudlet) ev.getData();
	                cloudletId = cl.getCloudletId();
	                userId = cl.getUserId();

	                status = getPAMVmAllocationPolicy().getHost(vmId, userId).getVm(userId, vmId).getCloudletScheduler().getCloudletStatus(cloudletId);
	            }
	            catch (Exception e) {
	                Log.printLine(getName() +
	                        ": Error in processing CloudSimTags.CLOUDLET_STATUS");
	                Log.printLine( e.getMessage() );
	                return;
	            }
	        }
	        catch (Exception e) {
	            Log.printLine(getName() +
	                    ": Error in processing CloudSimTags.CLOUDLET_STATUS");
	            Log.printLine( e.getMessage() );
	            return;
	        }

	        int[] array = new int[3];
	        array[0] = getId();
	        array[1] = cloudletId;
	        array[2] = status;

	        int tag = CloudSimTags.CLOUDLET_STATUS;
	        sendNow(userId, tag, array);
	    }

	    /**
	     * Here all the method related to VM requests will be received and forwarded to the related method.
	     *
	     * @param ev the received event
	     *
	     * @pre $none
	     * @post $none
	     */
	    protected void processOtherEvent(SimEvent ev) {
	        if (ev == null){
	            Log.printLine(getName() + ".processOtherEvent(): Error - an event is null.");
	        }
	    }
	   
		protected void processVmCreate(SimEvent ev, boolean ack) {
	    	PAMVm vm = (PAMVm) ev.getData();

	 	    boolean result = getPAMVmAllocationPolicy().allocateHostForVm(vm);

	 	    if (ack) {
	 	       int[] data = new int[3];
	           data[0] = getId();
	 	       data[1] = vm.getId();

	           if (result) {
	        	   data[2] = CloudSimTags.TRUE;
	           } else {
	        	   data[2] = CloudSimTags.FALSE;
	           }
			 // sendNow(vm.getUserId(), CloudSimTags.VM_CREATE_ACK, data);
	          sendNow(getDcManager().getId(), CloudSimTags.VM_CREATE_ACK, data);
	 	    }

	 	    if (result) {
				double amount = 0.0;
				if (getDebts().containsKey(vm.getUserId())) {
					amount = getDebts().get(vm.getUserId());
				}
				amount += getCharacteristics().getCostPerMem() * vm.getRam();
				amount += getCharacteristics().getCostPerStorage() * vm.getSize();

				getDebts().put(vm.getUserId(), amount);

				getVmList().add(vm);

				vm.updateVmProcessing(CloudSim.clock(), getPAMVmAllocationPolicy().getHost(vm).getVmScheduler().getAllocatedMipsForVm(vm));
	 	    } 	    
	 	    
	    }
		
	    protected void processVmDestroy(SimEvent ev, boolean ack) {
	    	PAMVm vm = (PAMVm) ev.getData();
			getPAMVmAllocationPolicy().deallocateHostForVm(vm);

			if (ack) {
				int[] data = new int[3];
				data[0] = getId();
				data[1] = vm.getId();
				data[2] = CloudSimTags.TRUE;

				//sendNow(vm.getUserId(),	CloudSimTags.VM_DESTROY_ACK, data);
				sendNow(getDcManager().getId(),	CloudSimTags.VM_DESTROY_ACK, data);
				
			}

			getVmList().remove(vm);
	    }
	    
	    /**
	     * Process the event for an User/Broker who wants to migrate a VM.
	     * This PowerDatacenter will then send the status back to the User/Broker.
	     * @param ev   a Sim_event object
	     * @pre ev != null
	     * @post $none
	     */
		protected void processVmMigrate(SimEvent ev, boolean ack) {
			Object tmp = ev.getData();
			if (!(tmp instanceof Map<?, ?>)) {
				throw new ClassCastException("The data object must be Map<String, Object>");
			}

			@SuppressWarnings("unchecked")
			Map<String, Object> migrate = (HashMap<String, Object>) tmp;

			PAMVm vm = (PAMVm) migrate.get("vm");
			PAMHost host = (PAMHost) migrate.get("host");

			getPAMVmAllocationPolicy().deallocateHostForVm(vm);
			host.removeMigratingInVm(vm);
			boolean result = getPAMVmAllocationPolicy().allocateHostForVm(vm, host);
			if (!result) {
				Log.printLine("Allocation failed");
			}

			if (ack) {
				int[] data = new int[3];
				data[0] = getId();
				data[1] = vm.getId();

				if (result) {
					data[2] = CloudSimTags.TRUE;
				} else {
					data[2] = CloudSimTags.FALSE;
				}
				sendNow(ev.getSource(), CloudSimTags.VM_CREATE_ACK, data);
			}

			double amount=0.0;
			if (debts.containsKey(vm.getUserId())) {
				amount = debts.get(vm.getUserId());
			}

			amount += getCharacteristics().getCostPerMem() * vm.getRam();
			amount += getCharacteristics().getCostPerStorage() * vm.getSize();

			debts.put(vm.getUserId(), amount);

			Log.formatLine("%.2f: Migration of VM #%d to Host #%d is completed", CloudSim.clock(), vm.getId(), host.getId());
			vm.setInMigration(false);
	    }

	    /**
	     * Processes a Cloudlet based on the event type.
	     *
	     * @param ev   a Sim_event object
	     * @param type event type
	     *
	     * @pre ev != null
	     * @pre type > 0
	     * @post $none
	     */
	    protected void processCloudlet(SimEvent ev, int type) {
	        int cloudletId = 0;
	        int userId = 0;
	        int vmId = 0;

	        try { // if the sender using cloudletXXX() methods
	            int data[] = (int[]) ev.getData();
	            cloudletId = data[0];
	            userId = data[1];
	            vmId = data[2];
	        }

	        // if the sender using normal send() methods
	        catch (ClassCastException c) {
	            try {
	                Cloudlet cl = (Cloudlet) ev.getData();
	                cloudletId = cl.getCloudletId();
	                userId = cl.getUserId();
	                vmId = cl.getVmId();
	            } catch (Exception e) {
	                Log.printLine(super.getName() + ": Error in processing Cloudlet");
	                Log.printLine(e.getMessage());
	                return;
	            }
	        } catch (Exception e) {
	            Log.printLine(super.getName() + ": Error in processing a Cloudlet.");
	            Log.printLine( e.getMessage() );
	            return;
	        }

	        // begins executing ....
			switch (type) {
				case CloudSimTags.CLOUDLET_CANCEL:
					processCloudletCancel(cloudletId, userId, vmId);
					break;

				case CloudSimTags.CLOUDLET_PAUSE:
					processCloudletPause(cloudletId, userId, vmId, false);
					break;

				case CloudSimTags.CLOUDLET_PAUSE_ACK:
					processCloudletPause(cloudletId, userId, vmId, true);
					break;

				case CloudSimTags.CLOUDLET_RESUME:
					processCloudletResume(cloudletId, userId, vmId, false);
					break;

				case CloudSimTags.CLOUDLET_RESUME_ACK:
					processCloudletResume(cloudletId, userId, vmId, true);
					break;
				default:
					break;
			}

	    }

	    /**
	     * Process the event for an User/Broker who wants to move a Cloudlet.
	     *
	     * @param receivedData   information about the migration
	     * @param type  event tag
	     *
	     * @pre receivedData != null
	     * @pre type > 0
	     * @post $none
	     */
	    protected void processCloudletMove(int[] receivedData, int type) {
	    	updateCloudletProcessing();

	    	int[] array = receivedData;
	    	int cloudletId = array[0];
	    	int userId = array[1];
	    	int vmId = array[2];
	    	int vmDestId = array[3];
	    	int destId = array[4];

	    	//get the cloudlet
	    	Cloudlet cl = getPAMVmAllocationPolicy().getHost(vmId, userId).getVm(userId, vmId).getCloudletScheduler().cloudletCancel(cloudletId);

	    	boolean failed=false;
			if (cl == null) {// cloudlet doesn't exist
				failed = true;
			} else {
				// has the cloudlet already finished?
				if (cl.getCloudletStatus() == Cloudlet.SUCCESS) {// if yes, send it back to user
					int[] data = new int[3];
					data[0] = this.getId();
					data[1] = cloudletId;
					data[2] = 0;
					sendNow(cl.getUserId(), CloudSimTags.CLOUDLET_SUBMIT_ACK, data);
					sendNow(cl.getUserId(), CloudSimTags.CLOUDLET_RETURN, cl);
				}

				// prepare cloudlet for migration
				cl.setVmId(vmDestId);

				// the cloudlet will migrate from one vm to another does the destination VM exist?
				if (destId == this.getId()) {
					Vm vm = getPAMVmAllocationPolicy().getHost(vmDestId, userId).getVm(userId, vmDestId);
					if (vm == null) {
						failed = true;
					} else {
						double fileTransferTime = predictFileTransferTime(cl.getRequiredFiles()); // time to transfer the files
						vm.getCloudletScheduler().cloudletSubmit(cl, fileTransferTime);
					}
				} else {// the cloudlet will migrate from one resource to another
					int tag = ((type == CloudSimTags.CLOUDLET_MOVE_ACK) ? CloudSimTags.CLOUDLET_SUBMIT_ACK : CloudSimTags.CLOUDLET_SUBMIT);
					sendNow(destId, tag, cl);
				}
			}

			if (type == CloudSimTags.CLOUDLET_MOVE_ACK) {// send ACK if requested
				int[] data = new int[3];
				data[0] = this.getId();
				data[1] = cloudletId;
				if (failed) {
					data[2] = 0;
				} else {
					data[2] = 1;
				}
				sendNow(cl.getUserId(), CloudSimTags.CLOUDLET_SUBMIT_ACK, data);
			}
	    }

	    /**
	     * Processes a Cloudlet submission.
	     *
	     * @param ev  a SimEvent object
	     * @param ack  an acknowledgement
	     *
	     * @pre ev != null
	     * @post $none
	     */
	    protected void processCloudletSubmit(SimEvent ev, boolean ack) {
	    	updateCloudletProcessing();

	        try {
	            // gets the Cloudlet object
	            Cloudlet cl = (Cloudlet) ev.getData();

	            // checks whether this Cloudlet has finished or not
	            if (cl.isFinished()){
	                String name = CloudSim.getEntityName(cl.getUserId());
	                Log.printLine(getName()+": Warning - Cloudlet #"+cl.getCloudletId()+" owned by "+name+" is already completed/finished.");
	                Log.printLine("Therefore, it is not being executed again");
	                Log.printLine();

	                // NOTE: If a Cloudlet has finished, then it won't be processed.
	                // So, if ack is required, this method sends back a result.
	                // If ack is not required, this method don't send back a result.
	                // Hence, this might cause CloudSim to be hanged since waiting
	                // for this Cloudlet back.
	                if (ack) {
	                    int[] data = new int[3];
	                    data[0] = getId();
	                    data[1] = cl.getCloudletId();
	                    data[2] = CloudSimTags.FALSE;

	                    // unique tag = operation tag
	                    int tag = CloudSimTags.CLOUDLET_SUBMIT_ACK;
	                    sendNow(cl.getUserId(), tag, data);
	                }

	                sendNow(cl.getUserId(), CloudSimTags.CLOUDLET_RETURN, cl);

	                return;
	            }

	            // process this Cloudlet to this CloudResource
	            cl.setResourceParameter(getId(), getCharacteristics().getCostPerSecond(), getCharacteristics().getCostPerBw());

	            int userId = cl.getUserId();
	            int vmId = cl.getVmId();

	            double fileTransferTime = predictFileTransferTime(cl.getRequiredFiles()); //time to transfer the files

	            PAMHost host = getPAMVmAllocationPolicy().getHost(vmId, userId);
	            Vm vm = host.getVm(vmId, userId);
	            CloudletScheduler scheduler = vm.getCloudletScheduler();
	            double estimatedFinishTime = scheduler.cloudletSubmit(cl,fileTransferTime);

	            //if (estimatedFinishTime > 0.0 && estimatedFinishTime < getSchedulingInterval()) { //if this cloudlet is in the exec queue
	            if (estimatedFinishTime > 0.0) { //if this cloudlet is in the exec queue
	            	//double estimatedFinishTime = (cl.getCloudletTotalLength()/(capacity*cl.getPesNumber())); //time to process the cloudlet
	            	//Log.printLine(estimatedFinishTime+"="+gl.getCloudletLength()+"/("+capacity+"*"+gl.getNumPE()+")");
	            	estimatedFinishTime += fileTransferTime;
	            	//estimatedFinishTime += CloudSim.clock();
	            	//Log.printLine(CloudSim.clock()+": Next event scheduled to +"+estimatedFinishTime);
	            	send(getId(), estimatedFinishTime, CloudSimTags.VM_DATACENTER_EVENT);
	            }

	        }
	        catch (ClassCastException c) {
	            Log.printLine(getName() + ".processCloudletSubmit(): " + "ClassCastException error.");
	            c.printStackTrace();
	        }
	        catch (Exception e) {
	            Log.printLine(getName() + ".processCloudletSubmit(): " + "Exception error.");
	            e.printStackTrace();
	        }

	    	checkCloudletCompletion();
	    }

	    /**
	     * Predict file transfer time.
	     *
	     * @param requiredFiles the required files
	     *
	     * @return the double
	     */
	    protected double predictFileTransferTime(List<String> requiredFiles) {
			double time = 0.0;

			Iterator<String> iter = requiredFiles.iterator();
			while (iter.hasNext()) {
				String fileName = iter.next();
				for (int i = 0; i < getStorageList().size(); i++) {
					Storage tempStorage = getStorageList().get(i);
					File tempFile = tempStorage.getFile(fileName);
					if (tempFile != null) {
						time += tempFile.getSize() / tempStorage.getMaxTransferRate();
						break;
					}
				}
			}
			return time;
		}

		/**
		 * Processes a Cloudlet resume request.
		 *
		 * @param cloudletId  resuming cloudlet ID
		 * @param userId  ID of the cloudlet's owner
		 * @param ack $true if an ack is requested after operation
		 * @param vmId the vm id
		 *
		 * @pre $none
		 * @post $none
		 */
		protected void processCloudletResume(int cloudletId, int userId, int vmId, boolean ack) {
			double eventTime = getPAMVmAllocationPolicy().getHost(vmId,userId).getVm(userId, vmId).getCloudletScheduler().cloudletResume(cloudletId);

			boolean status = false;
			if (eventTime > 0.0) { //if this cloudlet is in the exec queue
				status = true;
				if (eventTime > CloudSim.clock()) {
					schedule(getId(), eventTime, CloudSimTags.VM_DATACENTER_EVENT);
				}
	        }

			if (ack) {
				int[] data = new int[3];
				data[0] = getId();
				data[1] = cloudletId;
				if (status) {
					data[2] = CloudSimTags.TRUE;
				} else {
					data[2] = CloudSimTags.FALSE;
				}
				sendNow(userId, CloudSimTags.CLOUDLET_RESUME_ACK, data);
			}
		}

		/**
		 * Processes a Cloudlet pause request.
		 *
		 * @param cloudletId  resuming cloudlet ID
		 * @param userId  ID of the cloudlet's owner
		 * @param ack $true if an ack is requested after operation
		 * @param vmId the vm id
		 *
		 * @pre $none
		 * @post $none
		 */
		protected void processCloudletPause(int cloudletId, int userId, int vmId, boolean ack) {
			boolean status = getPAMVmAllocationPolicy().getHost(vmId,userId).getVm(userId, vmId).getCloudletScheduler().cloudletPause(cloudletId);

			if (ack) {
				int[] data = new int[3];
				data[0] = getId();
				data[1] = cloudletId;
				if (status) {
					data[2] = CloudSimTags.TRUE;
				} else {
					data[2] = CloudSimTags.FALSE;
				}
				sendNow(userId, CloudSimTags.CLOUDLET_PAUSE_ACK, data);
			}
		}

		/**
		 * Processes a Cloudlet cancel request.
		 *
		 * @param cloudletId  resuming cloudlet ID
		 * @param userId  ID of the cloudlet's owner
		 * @param vmId the vm id
		 *
		 * @pre $none
		 * @post $none
		 */
		protected void processCloudletCancel(int cloudletId, int userId, int vmId) {
			Cloudlet cl = getPAMVmAllocationPolicy().getHost(vmId,userId).getVm(userId, vmId).getCloudletScheduler().cloudletCancel(cloudletId);

	        sendNow(userId, CloudSimTags.CLOUDLET_CANCEL, cl);
		}

		/**
		 * Updates processing of each cloudlet running in this PowerDatacenter. It is necessary because
		 * Hosts and VirtualMachines are simple objects, not entities. So, they don't receive events
		 * and updating cloudlets inside them must be called from the outside.
		 *
		 * @pre $none
		 * @post $none
		 */
		protected void updateCloudletProcessing() {
			//Log.printLine(CloudSim.clock()+": PowerDatacenter #"+this.get_id()+": updating cloudlet processing.......................................");
			//if some time passed since last processing
			if (CloudSim.clock() > this.getLastProcessTime()) {
				List<? extends PAMHost> list = getPAMVmAllocationPolicy().getHostList();
				double smallerTime = Double.MAX_VALUE;
				//for each host...
				for (int i = 0; i < list.size(); i++) {
					PAMHost host = list.get(i);
					double time = host.updateVmsProcessing(CloudSim.clock());//inform VMs to update processing
					//what time do we expect that the next cloudlet will finish?
					if (time < smallerTime) {
						smallerTime = time;
					}
				}
				//schedules an event to the next time, if valid
				//if (smallerTime > CloudSim.clock() + 0.01 && smallerTime != Double.MAX_VALUE && smallerTime < getSchedulingInterval()) {
				if (smallerTime > CloudSim.clock() + 0.01 && smallerTime != Double.MAX_VALUE) {
					schedule(getId(), (smallerTime - CloudSim.clock()), CloudSimTags.VM_DATACENTER_EVENT);
				}
				setLastProcessTime(CloudSim.clock());
			}
		}

		/**
		 * Verifies if some cloudlet inside this PowerDatacenter already finished.
		 * If yes, send it to the User/Broker
		 *
		 * @pre $none
		 * @post $none
		 */
		protected void checkCloudletCompletion() {
			List<? extends PAMHost> list = getPAMVmAllocationPolicy().getHostList();
			for (int i = 0; i < list.size(); i++) {
				PAMHost host = list.get(i);
				for (Vm vm : host.getVmList()) {
					while (vm.getCloudletScheduler().isFinishedCloudlets()){
						Cloudlet cl = vm.getCloudletScheduler().getNextFinishedCloudlet();
						if (cl != null) {
							sendNow(cl.getUserId(), CloudSimTags.CLOUDLET_RETURN, cl);
						}
					}
				}
			}
		}

	   /**
	    * Adds a file into the resource's storage before the experiment starts.
	    * If the file is a master file, then it will be registered to the RC when
	    * the experiment begins.
	    *
	    * @param file  a DataCloud file
	    *
	    * @return a tag number denoting whether this operation is a success or not
	    *
	    * @see CloudSim.datagrid.DataCloudTags#FILE_ADD_SUCCESSFUL
	    * @see CloudSim.datagrid.DataCloudTags#FILE_ADD_ERROR_EMPTY
	    */
	    public int addFile(File file) {
	        if (file == null) {
	            return DataCloudTags.FILE_ADD_ERROR_EMPTY;
	        }

	        if (contains(file.getName())) {
	            return DataCloudTags.FILE_ADD_ERROR_EXIST_READ_ONLY;
	        }

	        // check storage space first
	        if (getStorageList().size() <= 0) {
	            return DataCloudTags.FILE_ADD_ERROR_STORAGE_FULL;
	        }

	        Storage tempStorage = null;
	        int msg = DataCloudTags.FILE_ADD_ERROR_STORAGE_FULL;

	        for (int i = 0; i < getStorageList().size(); i++) {
	            tempStorage = getStorageList().get(i);
	            if (tempStorage.getAvailableSpace() >= file.getSize()) {
	                tempStorage.addFile(file);
	                msg = DataCloudTags.FILE_ADD_SUCCESSFUL;
	                break;
	            }
	        }

	        return msg;
	    }

	    /**
	     * Checks whether the resource has the given file.
	     *
	     * @param file  a file to be searched
	     *
	     * @return <tt>true</tt> if successful, <tt>false</tt> otherwise
	     */
	    protected boolean contains(File file) {
	        if (file == null) {
	            return false;
	        }
	        return contains( file.getName() );
	    }

	    /**
	     * Checks whether the resource has the given file.
	     *
	     * @param fileName  a file name to be searched
	     *
	     * @return <tt>true</tt> if successful, <tt>false</tt> otherwise
	     */
	    protected boolean contains(String fileName) {
	        if (fileName == null || fileName.length() == 0) {
	            return false;
	        }

	        Iterator<Storage> it = getStorageList().iterator();
	        Storage storage = null;
	        boolean result = false;

	        while (it.hasNext()) {
	            storage = it.next();
	            if (storage.contains(fileName)) {
	                result = true;
	                break;
	            }
	        }

	        return result;
	    }

	    /**
	     * Deletes the file from the storage. Also, check whether it is
	     * possible to delete the file from the storage.
	     *
	     * @param fileName      the name of the file to be deleted
	     *
	     * @return the error message as defined in
	     * {@link CloudSim.datagrid.DataCloudTags}
	     *
	     * @see CloudSim.datagrid.DataCloudTags#FILE_DELETE_SUCCESSFUL
	     * @see CloudSim.datagrid.DataCloudTags#FILE_DELETE_ERROR_ACCESS_DENIED
	     * @see CloudSim.datagrid.DataCloudTags#FILE_DELETE_ERROR
	     */
	    private int deleteFileFromStorage(String fileName) {
	        Storage tempStorage = null;
	        File tempFile = null;
	        int msg = DataCloudTags.FILE_DELETE_ERROR;

	        for (int i = 0; i < getStorageList().size(); i++) {
	            tempStorage = getStorageList().get(i);
	            tempFile = tempStorage.getFile(fileName);
	            tempStorage.deleteFile(fileName, tempFile);
	            msg = DataCloudTags.FILE_DELETE_SUCCESSFUL;
	        } // end for

	        return msg;
	    }

		/**
		 * Prints the debts.
		 */
		public void printDebts() {
			Log.printLine("*****PowerDatacenter: "+this.getName()+"*****");
			Log.printLine("User id\t\tDebt");

			Set<Integer> keys = getDebts().keySet();
			Iterator<Integer> iter = keys.iterator();
			DecimalFormat df = new DecimalFormat("#.##");
			while (iter.hasNext()) {
				int key = iter.next();
				double value = getDebts().get(key);
				Log.printLine(key+"\t\t"+df.format(value));
			}
			Log.printLine("**********************************");
		}

		/* (non-Javadoc)
		 * @see cloudsim.core.SimEntity#shutdownEntity()
		 */
		@Override
		public void shutdownEntity() {
	        Log.printLine(getName() + " is shutting down...");
		}


		/**
		 * Gets the host list.
		 *
		 * @return the host list
		 */
		@SuppressWarnings("unchecked")
		public <T extends PAMHost> List<T> getHostList() {
			return (List<T>) getCharacteristics().getHostList();
		}

		/**
		 * Gets the characteristics.
		 *
		 * @return the characteristics
		 */
		protected PAMDatacenterCharacteristics getCharacteristics() {
			return characteristics;
		}

		/**
		 * Sets the characteristics.
		 *
		 * @param characteristics the new characteristics
		 */
		protected void setCharacteristics(PAMDatacenterCharacteristics characteristics) {
			this.characteristics = characteristics;
		}

		/**
		 * Gets the regional cis name.
		 *
		 * @return the regional cis name
		 */
		protected String getRegionalCisName() {
			return regionalCisName;
		}

		/**
		 * Sets the regional cis name.
		 *
		 * @param regionalCisName the new regional cis name
		 */
		protected void setRegionalCisName(String regionalCisName) {
			this.regionalCisName = regionalCisName;
		}

		/**
		 * Gets the vm allocation policy.
		 *
		 * @return the vm allocation policy
		 */
		public PAMVmAllocationPolicy getPAMVmAllocationPolicy() {
			return vmAllocationPolicy;
		}

		/**
		 * Sets the vm allocation policy.
		 *
		 * @param vmAllocationPolicy the new vm allocation policy
		 */
		protected void setPAMVmAllocationPolicy(PAMVmAllocationPolicy vmAllocationPolicy) {
			this.vmAllocationPolicy = vmAllocationPolicy;
		}

		/**
		 * Gets the last process time.
		 *
		 * @return the last process time
		 */
		protected double getLastProcessTime() {
			return lastProcessTime;
		}

		/**
		 * Sets the last process time.
		 *
		 * @param lastProcessTime the new last process time
		 */
		protected void setLastProcessTime(double lastProcessTime) {
			this.lastProcessTime = lastProcessTime;
		}

		/**
		 * Gets the debts.
		 *
		 * @return the debts
		 */
		protected Map<Integer, Double> getDebts() {
			return debts;
		}

		/**
		 * Sets the debts.
		 *
		 * @param debts the debts
		 */
		protected void setDebts(Map<Integer, Double> debts) {
			this.debts = debts;
		}

		/**
		 * Gets the storage list.
		 *
		 * @return the storage list
		 */
		protected List<Storage> getStorageList() {
			return storageList;
		}

		/**
		 * Sets the storage list.
		 *
		 * @param storageList the new storage list
		 */
		protected void setStorageList(List<Storage> storageList) {
			this.storageList = storageList;
		}

		/**
		 * Gets the vm list.
		 *
		 * @return the vm list
		 */
		@SuppressWarnings("unchecked")
		public <T extends Vm> List<T> getVmList() {
			return (List<T>) vmList;
		}

		/**
		 * Sets the vm list.
		 *
		 * @param vmList the new vm list
		 */
		protected <T extends Vm> void setVmList(List<T> vmList) {
			this.vmList = vmList;
		}

		/**
		 * Gets the scheduling interval.
		 *
		 * @return the scheduling interval
		 */
		protected double getSchedulingInterval() {
			return schedulingInterval;
		}

		/**
		 * Sets the scheduling interval.
		 *
		 * @param schedulingInterval the new scheduling interval
		 */
		protected void setSchedulingInterval(double schedulingInterval) {
			this.schedulingInterval = schedulingInterval;
		}
		public void startEntity() {
			Log.printLine(getName() + " is starting...");
		}
		
		public DCManager getDcManager() {
			return dcManager;
		}


		public void setDcManager(DCManager dcManager) {
			this.dcManager = dcManager;
		}
		
		public int vmsCreatedAcrossChipInDC(){
			int noOfVms =0;
			for(PAMHost host:getHostList()){
				noOfVms+=host.getVmScheduler().vmsCreatedAcrossChip();
			}
			return noOfVms;
		}
		
		public int vmsCreatedWithinChipInDC(){
			int noOfVms =0;
			for(PAMHost host:getHostList()){
				noOfVms+=host.getVmScheduler().vmsCreatedWithinChip();
			}
			return noOfVms;
		}


}
